﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using Cms.Domain;
using Cms.Domain.RepositoryInterfaces;
using Cms.Domain.Wrappers;
using NHibernate;
using NHibernate.Criterion;
using NHibernate.Transform;
using Convert = iLib.Utils.Convert;

namespace Cms.Data
{
    public class PageRepository : BaseRepository, IPageRepository
    {
        public PageRepository()
        {
        }
        
        public PageRepository(ISession session)
            : base(session)
        {
        }

        #region IPageRepository Members

        public Page GetPage(int id)
        {
            return Session.Get<Page>(id);
        }

        public Page GetPageFromFrontEnd(int id)
        {
            var page = Session.CreateQuery("from Page p where p.ID = " + id).SetCacheable(true);
            string hql = "from Page p " +
                         "left join fetch p.Content pagecontentlink " +
                         "left join fetch pagecontentlink.Content pagecontent " +
                         "left join fetch pagecontent.Translations pagetranslation " +
                         "left join fetch pagetranslation.PublishedVersion where p.ID = " + id;
            var pageContent = Session.CreateQuery(hql).SetCacheable(true);
            hql = "from Page p left join fetch p.RequiredUserGroups where p.ID = " + id;
            var requiredUserGroups = Session.CreateQuery(hql).SetCacheable(true);
            hql = "from Page p left join fetch p.DeselectedUserGroups where p.ID = " + id;
            var deselectedUserGroups = Session.CreateQuery(hql).SetCacheable(true);
            hql = "from Page p left join fetch p.MasterTemplate mt " +
                    "left join fetch mt.Content mastertemplatecontentlink " +
                    "left join fetch mastertemplatecontentlink.Content mastertemplatecontent " +
                    "left join fetch mastertemplatecontent.Translations mastertemplatetranslation " +
                    "left join fetch mastertemplatetranslation.PublishedVersion where p.ID = " + id;
            var masterTemplateContent = Session.CreateQuery(hql).SetCacheable(true);
            hql = "from Page p left join fetch p.MasterTemplate mt " +
                  "left join fetch mt.AssociatedStylesheets associatedstylesheets " +
                  "left join fetch associatedstylesheets.Stylesheet stylesheet " +
                  "left join fetch stylesheet.SourceCode sourcecode " +
                  "left join fetch sourcecode.CurrentVersion where p.ID = " + id;
            var stylesheets = Session.CreateQuery(hql).SetCacheable(true);
            hql = "from Page p left join fetch p.DetailTemplate dt " +
                    "left join fetch dt.Content detailtemplatecontentlink " +
                    "left join fetch detailtemplatecontentlink.Content detailtemplatecontent " +
                    "left join fetch detailtemplatecontent.Translations detailtemplatetranslation " +
                    "left join fetch detailtemplatetranslation.PublishedVersion where p.ID = " + id;
            var detailTemplateContent = Session.CreateQuery(hql).SetCacheable(true);
            IList result = Session.CreateMultiQuery()
                                        .Add(page)
                                        .Add(pageContent)
                                        .Add(requiredUserGroups)
                                        .Add(deselectedUserGroups)
                                        .Add(masterTemplateContent)
                                        .Add(stylesheets)
                                        .Add(detailTemplateContent)
                                        .SetCacheable(true)
                                        .List();
            var pages = result[0] as ArrayList;
            if (pages != null && pages.Count > 0)
            {
                return (Page)pages[0];
            }
            return null;
        }

        public void SavePage(Page page)
        {
            SaveEntity(page, "PageRepository.SavePage");
        }

        public void SaveApplicationPage(ApplicationPage page)
        {
            SaveEntity(page, "PageRepository.SaveApplicationPage");
        }

        public IList<Page> GetPages(int siteID, int folderID)
        {
            return Session.CreateCriteria(typeof (Page))
                    .SetFetchMode("MasterTemplate", FetchMode.Join)
                    .SetFetchMode("DetailTemplate", FetchMode.Join)
                    .Add(Restrictions.Eq("Site.ID", siteID))
                    .Add(folderID > 0 ? Restrictions.Eq("Folder.ID", folderID) : Restrictions.IsNull("Folder.ID"))
                    .AddOrder(new NHibernate.Criterion.Order("Name", true))
                    //.SetResultTransformer(new DistinctRootEntityResultTransformer())
                    .SetCacheable(true)
                    .List<Page>();
        }

        public IList<Page> ListPages(int categoryID, int skipFirstCount, int returnCount, ContentSortMode sortMode, SortDirection sortDirection)
        {
            string hql = "from Page p where exists (from p.Categories pc where pc.ID = " + categoryID + ") " +
                         "and (p.StartPublishing = null or p.StartPublishing <= :now) " +
                         "and (p.EndPublishing = null or p.EndPublishing >= :now) " +
                         "order by ";
            switch (sortMode)
            {
                case ContentSortMode.PublishedTimestamp:
                    hql += "p.ID";
                    break;
                case ContentSortMode.SortKey:
                    hql += "p.SortKey";
                    break;
            }
            if (sortDirection == SortDirection.Descending)
                hql += " desc";
            return Session.CreateQuery(hql)
                .SetParameter("now", DateTime.Now)
                .SetCacheable(true)
                .List<Page>();
        }

        public void DeletePage(int id)
        {
            DeleteHql("from Page where Id = " + id, "PageRepository.DeletePage");
        }

        public void DeleteCategory(Category category)
        {
            DeleteEntity(category, "PageRepository.DeleteCategory");
        }
        
        public Category GetCategory(int id)
        {
            return Session.Get<Category>(id);
        }
        
        public void SaveCategory(Category category)
        {
            SaveEntity(category, "PageRepository.SaveCategory");
        }
        
        public bool CategoryExists(string name, int id, int siteID)
        {
            string hql = "from PageCategory pc where pc.Name = :name and pc.Site.ID = " + siteID;
            if (id > 0)
                hql += " and pc.ID <> " + id;
            IList<Category> categories = Session.CreateQuery(hql)
                .SetString("name", name)
                .List<Category>();
            return categories.Count > 0;
        }

        public IList<SearchResultHit> Search(ref SearchContentFilter filter, int localeID, int searchOnlyInCategoryID, User user)
        {
            bool useFullTextSearch = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["UseFullTextSearch"]) 
                                        && ConfigurationManager.AppSettings["UseFullTextSearch"].ToLower() == "true";
            Page readMorePage = null;
            var searchResult = new List<SearchResultHit>();

            // Check search phrase
            string searchPhrase = Cms.Utilities.Html.Encode(filter.Freetext).Trim();
            if (searchPhrase == string.Empty)
            {
                return searchResult;
            }

            // Fetch ID of the ReadMore plugin. If not found, just return
            string hql = "from Plugin p where ClassName = 'Cms.Web.Framework.Plugins.ReadMore'";
            IList<Plugin> plugins = Session.CreateQuery(hql)
                                            .SetCacheable(true)
                                            .List<Plugin>();
            if (plugins.Count != 1)
            {
                return searchResult;
            }

            // Fetch all published content that contains a ReadMore plugin
            int readMorePluginID = plugins[0].ID;
            IList<ContentTranslationVersion> readMoreContentTranslationVersions;
            if (useFullTextSearch)
            {
                hql = "from ContentTranslationVersion ctv " +
                        "where ctv.ID = ctv.Translation.PublishedVersion.ID " +
                        " and ctv.Translation.Locale.ID = " + localeID +
                        " and contains(ctv.Html, :html)";
                readMoreContentTranslationVersions = Session.CreateQuery(hql)
                    .SetString("html", @"""<frediumplugin pluginid=""""" + readMorePluginID + @"""""""")
                    .SetCacheable(true)
                    .List<ContentTranslationVersion>();
            }
            else
            {
                hql = "from ContentTranslationVersion ctv " +
                        "where ctv.ID = ctv.Translation.PublishedVersion.ID " +
                        " and ctv.Translation.Locale.ID = " + localeID +
                        " and ctv.Html like :html";
                readMoreContentTranslationVersions = Session.CreateQuery(hql)
                    .SetString("html", @"%<frediumplugin pluginid=""" + readMorePluginID + @"""%")
                    .SetCacheable(true)
                    .List<ContentTranslationVersion>();
            }

            // Fetch application pages containing a ReadMore plugin
            hql = "select ap from ApplicationPage ap, SourceCodeVersion scv " +
                  "where scv.Code like :html and scv.Parent in elements(ap.Areas)";
            IList<ApplicationPage> readMoreApplicationPages = Session.CreateQuery(hql)
                .SetString("html", @"%<cms:readmore%")
                .SetCacheable(true)
                .List<ApplicationPage>();

            // Fetch all matching published versions
            IList<ContentTranslationVersion> hits;
            if (useFullTextSearch)
            {
                hql = "from ContentTranslationVersion ctv " +
                        "join fetch ctv.Translation t " +
                        "join fetch t.Content c " +
                        "where ctv.ID = ctv.Translation.PublishedVersion.ID " +
                        " and ctv.Translation.Locale.ID = " + localeID +
                        " and contains(ctv.Html, :html)";
                hits = Session.CreateQuery(hql)
                    .SetString("html", @"""" + searchPhrase.Replace(@"""", @"""""") + @"*""")
                    .SetResultTransformer(new DistinctRootEntityResultTransformer())
                    .SetMaxResults(200)
                    .List<ContentTranslationVersion>();


                // Preload Content-, Page- and Foldercollections to avoid SELECT N+1 issues
                ICollection contentIDs = hits.Select(x => x.Translation.Content.ID).ToList();
                var content = DetachedCriteria.For<Content>()
                    .Add(Restrictions.In("ID", contentIDs));
                var requiredUserGroups = DetachedCriteria.For<Content>()
                    .SetFetchMode("RequiredUserGroups", FetchMode.Eager)
                    .Add(Restrictions.In("ID", contentIDs));
                var pageLinks = DetachedCriteria.For<Content>()
                    .SetFetchMode("Pages", FetchMode.Eager)
                    .Add(Restrictions.In("ID", contentIDs));
                var pageLinkPages = DetachedCriteria.For<Content>()
                    .SetFetchMode("Pages.Page", FetchMode.Eager)
                    .Add(Restrictions.In("ID", contentIDs));
                var masterTemplateLinks = DetachedCriteria.For<Content>()
                    .SetFetchMode("MasterTemplates", FetchMode.Eager)
                    .Add(Restrictions.In("ID", contentIDs));
                var detailTemplateLinks = DetachedCriteria.For<Content>()
                    .SetFetchMode("DetailTemplates", FetchMode.Eager)
                    .Add(Restrictions.In("ID", contentIDs));
                var contentCategories = DetachedCriteria.For<Content>()
                    .SetFetchMode("Categories", FetchMode.Eager)
                    .Add(Restrictions.In("ID", contentIDs));
                var contentMultiCriteriaResult = Session.CreateMultiCriteria()
                    .Add(content)
                    .Add(requiredUserGroups)
                    .Add(pageLinks)
                    .Add(pageLinkPages)
                    .Add(masterTemplateLinks)
                    .Add(detailTemplateLinks)
                    .Add(contentCategories)
                    .SetCacheable(true)
                    .List();
                IList<Content> resultContent = ((IList)contentMultiCriteriaResult[0]).Cast<Content>().ToList();
                ICollection pageIDs = (from c in resultContent from pl in c.Pages select pl.Page.ID).ToList();
                var pages = DetachedCriteria.For<Page>()
                    .Add(Restrictions.In("ID", pageIDs));
                var pageRequiredUserGroups = DetachedCriteria.For<Page>()
                    .SetFetchMode("RequiredUserGroups", FetchMode.Eager)
                    .Add(Restrictions.In("ID", pageIDs));
                var pageDeselectedUserGroups = DetachedCriteria.For<Page>()
                    .SetFetchMode("DeselectedUserGroups", FetchMode.Eager)
                    .Add(Restrictions.In("ID", pageIDs));
                var pageFolder = DetachedCriteria.For<Page>()
                    .SetFetchMode("Folder", FetchMode.Eager)
                    .Add(Restrictions.In("ID", pageIDs));
                var pageMultiCriteriaResult = Session.CreateMultiCriteria()
                    .Add(pages)
                    .Add(pageRequiredUserGroups)
                    .Add(pageDeselectedUserGroups)
                    .Add(pageFolder)
                    .SetCacheable(true)
                    .List();
                IList<Page> resultPages = ((IList)pageMultiCriteriaResult[0]).Cast<Page>().ToList();
                ICollection folderIDs = (from p in resultPages select p.Folder.ID).ToList();
                var folders = DetachedCriteria.For<Folder>()
                    .Add(Restrictions.In("ID", folderIDs));
                var folderRequiredUserGroups = DetachedCriteria.For<Folder>()
                    .SetFetchMode("RequiredUserGroups", FetchMode.Eager)
                    .Add(Restrictions.In("ID", folderIDs));
                var folderDeselectedUserGroups = DetachedCriteria.For<Folder>()
                    .SetFetchMode("DeselectedUserGroups", FetchMode.Eager)
                    .Add(Restrictions.In("ID", folderIDs));
                Session.CreateMultiCriteria()
                    .Add(folders)
                    .Add(folderRequiredUserGroups)
                    .Add(folderDeselectedUserGroups)
                    .SetCacheable(true)
                    .List();
            }
            else
            {
                hql = "from ContentTranslationVersion ctv " +
                        "where ctv.ID = ctv.Translation.PublishedVersion.ID " +
                        " and ctv.Translation.Locale.ID = " + localeID +
                        " and ctv.Html like :html";
                hits = Session.CreateQuery(hql)
                    .SetString("html", "%" + searchPhrase + "%")
                    .SetResultTransformer(new DistinctRootEntityResultTransformer())
                    .List<ContentTranslationVersion>();
            }

            // Check each version to see if it qualifies as a hit
            foreach (ContentTranslationVersion ctv in hits)
            {
                // Ensure that the match was made against the textual content and not just something in the html code
                string plainText = Convert.ClearHtmlTags(ctv.Html, Convert.ClearHtmlAction.StripTags);
                if (!plainText.ToLower().Contains(searchPhrase.ToLower()))
                {
                    continue;
                }
                if (!ctv.Translation.Content.IsPublishedNow())
                {
                    continue;
                }
                if (ctv.Translation.Content.RequiredUserGroups.Count > 0)
                {
                    if (user == null)
                    {
                        continue;
                    }
                    Membership membership = user.Memberships.FirstOrDefault(m => m.IsActive());
                    if (membership == null || !ctv.Translation.Content.RequiredUserGroups.Contains(membership.UserGroup))
                    {
                        continue;
                    }
                }
                SearchResultHit hit;
                if (ctv.Translation.Content.Pages.Count == 0 && (readMoreContentTranslationVersions.Count > 0 || readMoreApplicationPages.Count > 0))
                {
                    if (ctv.Translation.Content.MasterTemplates.Count > 0 || ctv.Translation.Content.DetailTemplates.Count > 0)
                    {
                        continue;
                    }

                    // The content has no related pages, mastertemplates or detailtemplates. 
                    // Try to match it against a read-more page using categories
                    var allCategoryIDs = new List<int>();
                    foreach (var category in ctv.Translation.Content.Categories)
                    {
                        allCategoryIDs.AddRange(category.GetAllIDs());
                        Category parent = category.Parent;
                        while (parent != null)
                        {
                            allCategoryIDs.Add(parent.ID);
                            parent = parent.Parent;
                        }
                    }

                    foreach (ContentTranslationVersion readMore in readMoreContentTranslationVersions)
                    {
                        if (readMore.Translation.Content.Pages.Count == 0)
                        {
                            continue;
                        }
                        int readMoreCategoryID = -1;
                        Match pluginDeclarationMatch = Regex.Match(readMore.Html, "<frediumplugin[^>]*>", RegexOptions.IgnoreCase);
                        foreach (Capture capture in pluginDeclarationMatch.Captures)
                        {
                            if (capture.Value.IndexOf("Cms.Web.Framework.Plugins.ReadMore") < 0)
                            {
                                continue;
                            }
                            if (capture.Value.ToLower().IndexOf("categoryid=\"") <= 0)
                            {
                                continue;
                            }
                            string categoryDeclaration = capture.Value.Substring(capture.Value.ToLower().IndexOf("categoryid=\"") + 12);
                            categoryDeclaration = categoryDeclaration.Substring(0, categoryDeclaration.IndexOf("\""));
                            readMoreCategoryID = int.Parse(categoryDeclaration);
                            break;
                        }
                        if (!allCategoryIDs.Contains(readMoreCategoryID))
                        {
                            continue;
                        }
                        readMorePage = readMore.Translation.Content.Pages[0].Page;
                        break;
                    }
                    if (readMorePage == null)
                    {
                        foreach (ApplicationPage ap in readMoreApplicationPages)
                        {
                            int readMoreCategoryID = -1;
                            foreach (KeyValuePair<string, SourceCode> kvp in ap.Areas)
                            {
                                Match pluginDeclarationMatch = Regex.Match(kvp.Value.CurrentVersion.Code, "<cms:ReadMore[^>]*>", RegexOptions.IgnoreCase);
                                foreach (Capture capture in pluginDeclarationMatch.Captures)
                                {
                                    if (capture.Value.ToLower().IndexOf("categoryid=\"") <= 0)
                                    {
                                        continue;
                                    }
                                    string categoryDeclaration = capture.Value.Substring(capture.Value.ToLower().IndexOf("categoryid=\"") + 12);
                                    categoryDeclaration = categoryDeclaration.Substring(0, categoryDeclaration.IndexOf("\""));
                                    readMoreCategoryID = int.Parse(categoryDeclaration);
                                    break;
                                }
                                if (readMoreCategoryID > 0)
                                {
                                    break;
                                }
                            }
                            if (!allCategoryIDs.Contains(readMoreCategoryID))
                            {
                                continue;
                            }
                            readMorePage = ap;
                            break;
                        }
                    }
                    if (readMorePage != null)
                    {
                        bool addHit = true;
                        if (readMorePage.GetRequiredUserGroups().Count > 0)
                        {
                            if (user == null)
                            {
                                addHit = false;
                            }
                            else
                            {
                                Membership membership = user.Memberships.FirstOrDefault(m => m.IsActive());
                                if (membership == null || !readMorePage.GetRequiredUserGroups().Contains(membership.UserGroup))
                                {
                                    addHit = false;
                                }
                            }
                        }
                        if (addHit)
                        {
                            string title = ctv.Title;
                            if (string.IsNullOrEmpty(title))
                            {
                                title = ctv.Translation.Content.Name;
                            }
                            if (string.IsNullOrEmpty(title))
                            {
                                title = readMorePage.LocalizedProperties[localeID].Title;
                            }
                            hit = new SearchResultHit
                            {
                                Title = title,
                                Url = readMorePage.GetVirtualPath() + "?ContentID=" + ctv.Translation.Content.ID,
                                Html = ctv.Html
                            };
                            searchResult.Add(hit);
                        }
                    }
                }
                else
                {
                    foreach (PageContentLink pageLink in ctv.Translation.Content.Pages)
                    {
                        if (searchOnlyInCategoryID > 0 && !pageLink.Page.GetAllCategories().Any(c => c.ID == searchOnlyInCategoryID))
                        {
                            continue;
                        }
                        if (pageLink.Page.GetRequiredUserGroups().Count > 0)
                        {
                            if (user == null)
                            {
                                continue;
                            }
                            Membership membership = user.Memberships.FirstOrDefault(m => m.IsActive());
                            if (membership == null || !pageLink.Page.GetRequiredUserGroups().Contains(membership.UserGroup))
                            {
                                continue;
                            }
                        }
                        hit = new SearchResultHit
                        {
                            Title = pageLink.Page.LocalizedProperties[localeID].Title,
                            Url = pageLink.Page.GetVirtualPath(),
                            Html = ctv.Html
                        };
                        searchResult.Add(hit);
                    }
                }
            }
            filter.Count = searchResult.Count;
            int pageSize = (filter.CurrentPage * filter.PageSize) + filter.PageSize <= searchResult.Count
                               ? filter.PageSize
                               : searchResult.Count - (filter.CurrentPage * filter.PageSize);
            return searchResult.GetRange(filter.CurrentPage * filter.PageSize, pageSize);
        }

        public IList<Page> GetMostVisitedPages(int currentSiteID)
        {
            string hql = "from Page p where p.Site.ID = :currentSiteID order by p.Hits desc";
            return Session.CreateQuery(hql)
                .SetInt32("currentSiteID", currentSiteID)
                .SetMaxResults(10)
                .List<Page>();
        }

        public void SaveComment(Comment comment)
        {
            SaveEntity(comment, "PageRepository.SaveComment");
        }
        
        public Comment GetComment(int id)
        {
            return Session.Get<Comment>(id);
        }
        
        public IList<Comment> GetComments()
        {
            return Session.CreateCriteria(typeof(Comment)).List<Comment>();
        }

        #endregion
    }
}